
using System;
using System.Diagnostics;
using System.IO;
using System.Windows.Forms;

namespace OpenComposite.Base
{

	public class ListViewTraceListener : TraceListener
	{

		protected const string NO_CATEGORY = "...";
		protected const string FAIL_CAT = "Fatal Error";
		protected const string SEPARATOR = "=========================";

		// A constructor supplies values to these fields.
		protected ListView traceView;
		protected string appTitle;
		protected string logFileSpec;

		protected string indentBuffer;
		protected ListViewItem lastLineAdded;

		// These are buffers for data from the Write() overloads.
		protected string partialMessage;
		protected string initCategory;

		protected ColumnHeader hdrTimeStamp;
		protected ColumnHeader hdrCategory;
		protected ColumnHeader hdrMessage;

		// This constructor also supplies a listener name and writes it to the trace.
		public ListViewTraceListener(string name, ListView traceBox, string title) : base(name)
		{
			InitializeListener(traceBox, title);
			this.WriteLine(string.Format("Listener \"{0}\" initialized.", this.Name));
		}

		// This is the basic constructor.
		public ListViewTraceListener(ListView traceBox, string title) : base()
		{
			InitializeListener(traceBox, title);
		}

		// This constructor overload also supplies a log file name.
		public ListViewTraceListener(string name, ListView traceBox, string title, string logFile) :
			this(name, traceBox, title)
		{
			logFileSpec = logFile;
		}

		// This constructor overload also supplies listener and log file names.
		public ListViewTraceListener(ListView traceBox, string title, string logFile) :
			this(traceBox, title)
		{
			logFileSpec = logFile;
		}

		protected void InitializeListener(ListView traceBox, string title)
		{

			// Save a reference to the ListView and the application title.
			traceView = traceBox;
			appTitle = title;

			// Initialize and add the columns of the ListView.
			hdrTimeStamp = new ColumnHeader();
			hdrCategory = new ColumnHeader();
			hdrMessage = new ColumnHeader();

			hdrTimeStamp.Text = "Time";
			hdrTimeStamp.Width = 80;
			hdrCategory.Text = "Category";
			hdrCategory.Width = 88;
			hdrMessage.Text = "Trace message";
			hdrMessage.Width = 400;

			traceBox.Columns.AddRange(new ColumnHeader[] { hdrTimeStamp, hdrCategory, hdrMessage });
			traceBox.View = View.Details;
			ResetPartialMessage();
		}

		public override void Write(object obj)
		{
			if ( obj == null )
				this.Write("", NO_CATEGORY);
			else
				this.Write(obj.ToString(), NO_CATEGORY);
		}

		public override void Write(string message)
		{
			this.Write(message, NO_CATEGORY);
		}

		public override void Write(object obj, string category)
		{
			if ( obj == null )
				this.Write("", category);
			else
				this.Write(obj.ToString(), category);
		}

		public override void Write(string message, string category)
		{
			// Save the partial trace message and initial category for later use.
			partialMessage += message;
			if ( category != NO_CATEGORY )
				initCategory = category;
		}

		public override void WriteLine(object obj)
		{
			if ( obj == null )
				this.WriteLine((string)null, NO_CATEGORY);
			else
				this.WriteLine(obj.ToString(), NO_CATEGORY);
		}

		public override void WriteLine(string message)
		{
			// If the message is null, write a separator to the trace.
			if ( message == null ) {
				// Flush a partial trace message if it is present, 
				// then write the separator.
				this.Flush();
				traceView.Items.Add(new ListViewItem(
					new string[] { string.Empty, string.Empty, SEPARATOR }));
				this.NeedIndent = true;
			} else {
				// Otherwise, write the message with no category.
				this.WriteLine(message, NO_CATEGORY);
			}
		}

		public override void WriteLine(object obj, string category)
		{
			if ( obj == null )
				this.WriteLine("", category);
			else
				this.WriteLine(obj.ToString(), category);
		}

		public override void WriteLine(string message, string category)
		{
			// Form the indent string.
			this.WriteIndent();

			// If there is no partial trace message, just write the current message.
			if ( partialMessage == "" ) {
				lastLineAdded = new ListViewItem(new string[] { this.TimeStamp(), category, indentBuffer + message });
			} else {
				// Include the partial trace message in the written line.
				lastLineAdded = new ListViewItem(new string[] {this.TimeStamp(),
                (category != NO_CATEGORY ? category : initCategory),
                indentBuffer + partialMessage + message});
				ResetPartialMessage();
			}

			traceView.Items.Add(lastLineAdded);
			this.NeedIndent = true;
		}

		protected void ResetPartialMessage()
		{
			// Reset the partial trace message after calls to 
			// WriteLine() and Flush().
			partialMessage = "";
			initCategory = NO_CATEGORY;
		}

		public override void Flush()
		{
			// If there is a partial trace message waiting, write it.
			if ( partialMessage != "" ) {
				// Form the indent string, then make the 
				// partial trace message into a written line.
				this.WriteIndent();
				lastLineAdded = new ListViewItem(new string[] {TimeStamp(),
                                initCategory, indentBuffer + partialMessage});
				traceView.Items.Add(lastLineAdded);

				ResetPartialMessage();
				this.NeedIndent = true;
			}
		}

		public override void Fail(string message)
		{
			// Flush the partial trace message and 
			// inhibit indenting of the Fail message.
			this.Flush();
			this.NeedIndent = false;

			this.WriteLine(message, FAIL_CAT);
			MessageBox.Show(message, appTitle, MessageBoxButtons.OK, MessageBoxIcon.Stop);

			this.NeedIndent = true;
		}

		public override void Fail(string message, string detailMessage)
		{
			// Flush the partial trace message and 
			// inhibit indenting of the Fail message.
			this.Flush();
			this.NeedIndent = false;

			this.WriteLine(message, FAIL_CAT);

			// Handle the Trace.Fail( string ) overload, if necessary.
			if ( detailMessage == null ) {
				MessageBox.Show(message, appTitle, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
			} else {
				// Write the detailMessage to the ListView.
				traceView.Items.Add(new ListViewItem(
					new string[] { string.Empty, string.Empty, detailMessage }));
				MessageBox.Show(message + "\r\n\r\n" + detailMessage,
					appTitle, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
			}

			this.NeedIndent = true;
		}

		public override void Close()
		{
			// If a log file is specified, copy the ListView data to the file.
			if ( logFileSpec != "" ) {
				StreamWriter writer = File.AppendText(logFileSpec);

				// Copy the ListView rows to the log file.
				for ( int recNo = 0; recNo < traceView.Items.Count; recNo++ ) {
					ListViewItem currRec = traceView.Items[recNo];

					writer.WriteLine(string.Concat(currRec.SubItems[0].Text,
											"   ", currRec.SubItems[1].Text,
											"   ", currRec.SubItems[2].Text));
				}

				// Flush the log file buffer and close the file.
				writer.Flush();
				writer.Close();
			}

			base.Close();
		}

		protected override void WriteIndent()
		{
			// Generate the correct number of spaces for the indent string.
			if ( this.NeedIndent )
				indentBuffer = new string(' ', this.IndentLevel * this.IndentSize);
			else
				indentBuffer = "";
		}

		protected string TimeStamp()
		{
			return DateTime.Now.ToString("HH:mm:ss.fff");
		}
	}
}